/*
 * Copyright 2010 Google Inc.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.google.websocket;

import com.google.websocket.HttpWebSocketListener.ListenCallback;
import com.google.websocket.WebSocket.Callback;

import junit.framework.TestCase;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.SocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Set;

/**
 * Test for {@link HttpWebSocketListener}.
 */
public class HttpWebSocketListenerTest extends TestCase {

  private static final class CheckConnection implements ListenCallback {
    private final Class<? extends WebSocket> wsClass;
    private final String testMethod;

    public CheckConnection(Class<? extends WebSocket> wsClass) {
      this.wsClass = wsClass;
      String method = null;
      try {
        throw new RuntimeException("for stack trace");
      } catch (RuntimeException e) {
        method = e.getStackTrace()[1].getMethodName();
      }
      testMethod = method;
    }

    public void onConnection(final WebSocket socket,
        SocketAddress remoteSocketAddress) {
      assertTrue("Socket should be a " + wsClass.getName(),
          wsClass.isInstance(socket));
      try {
        socket.readOneMessage(new Callback() {
          public void onBinaryMessage(byte[] message, Set<ExtensionData> metadata)
              throws IOException {
            fail("binary message");
          }

          public void onClose() {
          }

          public void onFramingError() {
            fail("Framing error");
          }

          public void onTextMessage(String message,
              Set<ExtensionData> metadata) throws IOException {
            socket.sendTextMessage(metadata, "echo " + message);
          }
        });
      } catch (IOException e) {
        e.printStackTrace();
      } finally {
        try {
          socket.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }

    public void onException(SocketAddress remoteAddress, IOException e) {
      if (!e.getMessage().contains("closed")) {
        System.err.println("Server-side error for " + testMethod);
        e.printStackTrace();
      }
    }
  }

  private static class ListenerThread extends Thread {
    private final ServerSocket socket;

    private HttpCompatibleHandshake[] handshakes;

    private ListenCallback callback;
    
    public ListenerThread(ServerSocket socket,
        HttpCompatibleHandshake... handshakes) {
      this.socket = socket;
      this.handshakes = handshakes;
    }

    @Override
    public void run() {
      try {
        HttpWebSocketListener.listen(socket, new ListenCallback() {
          public void onConnection(WebSocket socket,
              SocketAddress remoteSocketAddress) {
            callback.onConnection(socket, remoteSocketAddress);
          }

          public void onException(SocketAddress remoteAddress, IOException e) {
            callback.onException(remoteAddress, e);
          }
          
        }, handshakes);
      } catch (IOException e) {
        throw new RuntimeException(e);
      }
    }

    public void setCallback(ListenCallback callback) {
      this.callback = callback;
    }
  }

  private ListenerThread listener;
  private ServerSocket socket;

  public void testAdamBarthHandshake() throws IOException, URISyntaxException {
    performHandshakeTest(new AdamBarthHandshake(),
        new CheckConnection(DraftWebSocket.class));
  }

  public void testDraftHandshake() throws IOException, URISyntaxException {
    performHandshakeTest(new DraftHandshake(),
        new CheckConnection(DraftWebSocket.class));
  }

  public void testV76Handshake() throws IOException, URISyntaxException {
    performHandshakeTest(new V76Handshake(),
        new CheckConnection(V76WebSocket.class));
  }

  @Override
  protected void setUp() throws Exception {
    super.setUp();
    socket = Util.getBoundServerSocket(0);
    listener = new ListenerThread(socket, new V76Handshake(),
        new DraftHandshake(), new AdamBarthHandshake());
    listener.setDaemon(true);
    listener.start();
  }

  @Override
  protected void tearDown() throws Exception {
    super.tearDown();
    socket.close();
  }
  
  private void performHandshakeTest(WebSocketHandshake handshake,
      ListenCallback callback) throws IOException, URISyntaxException {
    WebSocketConnector conn = new WebSocketConnector(handshake);
    listener.setCallback(callback);
    int port = socket.getLocalPort();
    // CHECKSTYLE_OFF - colon isn't part of an error message
    WebSocket socket = conn.connect(new URI("ws://localhost:" + port),
        "localhost:" + port);
    // CHECKSTYLE_ON
    socket.sendTextMessage(null, "hello");
    socket.readOneMessage(new Callback() {

      public void onBinaryMessage(byte[] message, Set<ExtensionData> metadata)
          throws IOException {
        fail("binary message");
      }

      public void onClose() {
        fail("close");
      }

      public void onFramingError() {
        fail("framing error");
      }

      public void onTextMessage(String message, Set<ExtensionData> metadata)
          throws IOException {
        assertEquals("echo hello", message);
      }
    });
    socket.close();
  }
}
